﻿using System;
namespace GSE.Network.UDP
{
    public enum PacketType
    {
        PacketTypeHead,
        PacketTypePartial,
        PacketTypeTail,
        PacketTypeHeadWithTail,
        PacketTypeAck,
        PacketTypeHeartbeat,
        PacketTypeReset,
        PacketTypePing,
        PacketTypeClose
    }

    public class Packet
    {
        public const byte CurrentProtoVerId = 1;
        public const UInt32 MaxDataLength = 4294967295;
        public const UInt16 PacketSize = 548;
        public const UInt16 MaxPacketSeqNo = 1023;
        protected bool isTail;
        protected bool isHead;
        protected byte[] payload;
        protected bool needAck;
        protected PacketType packetType;
        protected UInt16 seq;
        protected DateTime recvTime;
        protected string remoteAddr;
        protected UInt32 dataLength;
        protected UInt32 dataCRC;
        public Packet(byte[] data, string remoteAddress)
        {
            if (data.Length < 2)
            {
                throw new Exception("Packet length must be greater than 2");
            }
            byte[] headerBytes = new byte[2];
            Array.Copy(data, 0, headerBytes, 0, 2);
            Array.Reverse(headerBytes);
            var header = BitConverter.ToUInt16(headerBytes, 0);
            var verID = (byte)(header >> 14);
            if (verID != CurrentProtoVerId)
            {
                throw new Exception($"Invalid protocol ID {verID}.");
            }
            remoteAddr = remoteAddress;
            recvTime = DateTime.Now;
            needAck = (UInt16)(header << 2) >> 15 != 0;
            packetType = (PacketType)((UInt16)(header << 3) >> 13);
            seq = (UInt16)((UInt16)(header << 6) >> 6);

            switch (packetType)
            {
                case PacketType.PacketTypeHeartbeat:
                case PacketType.PacketTypeAck:
                case PacketType.PacketTypeReset:
                case PacketType.PacketTypePing:
                    break;
                case PacketType.PacketTypePartial:
                case PacketType.PacketTypeTail:
                    isTail = packetType == PacketType.PacketTypeTail;
                    payload = new byte[data.Length - 2];
                    Array.Copy(data, 2, payload, 0, payload.Length);
                    break;
                case PacketType.PacketTypeHead:
                case PacketType.PacketTypeHeadWithTail:
                    isHead = true;
                    isTail = packetType == PacketType.PacketTypeHeadWithTail;
                    if (data.Length < 10)
                    {
                        throw new Exception($"Head packet type[{packetType}] requires at least 10 bytes, but got only {data.Length}.");
                    }
                    byte[] dataLengthBytes = new byte[4];
                    Array.Copy(data, 2, dataLengthBytes, 0, 4);
                    Array.Reverse(dataLengthBytes);
                    dataLength = BitConverter.ToUInt32(dataLengthBytes, 0);
                    byte[] crcBytes = new byte[4];
                    Array.Copy(data, 6, crcBytes, 0, 4);
                    Array.Reverse(crcBytes);
                    dataCRC = BitConverter.ToUInt32(crcBytes, 0);
                    payload = new byte[data.Length - 10];
                    Array.Copy(data, 10, payload, 0, payload.Length);
                    break;
                default:
                    throw new Exception($"In valid packet type {packetType}.");
            }
        }

        public PacketType GetPacketType()
        {
            return packetType;
        }

        public byte[] GetPayload()
        {
            return payload;
        }

        public bool IsTail()
        {
            return isTail;
        }

        public bool IsHead()
        {
            return isHead;
        }

        public bool NeedAck()
        {
            return needAck;
        }

        public UInt16 GetSeqNo()
        {
            return seq;
        }

        public UInt32 GetCRC()
        {
            return dataCRC;
        }

        public UInt32 GetDataLength()
        {
            return dataLength;
        }

        public static byte[] NewHeartbeat()
        {
            UInt16 headerInt = CurrentProtoVerId << 14;
            headerInt += (UInt16)PacketType.PacketTypeHeartbeat << 10;
            var data = BitConverter.GetBytes(headerInt);
            Array.Reverse(data);
            return data;
        }
    }
}
